package com.inepex.inechart.client.axes;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.TreeMap;

import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;
import com.inepex.inechart.shared.axes.Axis;
import com.inepex.inechart.shared.axes.AxisDataType;
import com.inepex.inechart.shared.axes.Tick;
import com.inepex.inechart.shared.axes.TickFactory;

public class TickFactoryGWT extends TickFactory {
	DateTimeFormat dtf;
	NumberFormat nf;
	
	protected TreeMap<Axis, Double> deltas;
	protected TreeMap<Axis, TickSizePair> tickSizePairs;
	
	public TickFactoryGWT() {
		deltas = new TreeMap<Axis, Double>();
		tickSizePairs = new TreeMap<Axis, TickFactory.TickSizePair>();
	}

	@Override
	public String formatTickText(Tick tick, AxisDataType dataType) {
//		if(format.length() == 0){
//			return ((Double)value).toString();
//		}
		String formatted = "";
		try{
			switch (dataType) {
			case Number:
				nf = NumberFormat.getFormat(tick.getFormatString());
				formatted = nf.format(tick.getPosition());
				break;
			case Time:
				dtf = DateTimeFormat.getFormat(tick.getFormatString());
				formatted = dtf.format(new Date((long) tick.getPosition()));
				break;
			}
		}
		catch (Exception e) {
			formatted = ((Double)tick.getPosition()).toString();
		}
		return formatted;
	}
	
	public static String formatValue(AxisDataType axisDataType, double value, String format){
		if(format.length() == 0){
			return ((Double)value).toString();
		}
		String formatted = "";
		try{
			switch (axisDataType) {
			case Number:
				NumberFormat nf = NumberFormat.getFormat(format);
				formatted = nf.format(value);
				break;
			case Time:
				DateTimeFormat dtf = DateTimeFormat.getFormat(format);
				formatted = dtf.format(new Date((long)value));
				break;
			}
		}
		catch (Exception e) {
			formatted = ((Double)value).toString();
		}
		return formatted;
	}
	
	
	public ArrayList<Tick> autoCreateTicks(Axis axis, double from, double to){
		if(axis.getAxisDataType() == AxisDataType.Number){
			if(!deltas.containsKey(axis)){
				setAutoCreationParameters(axis, (int) Math.round(axis.isHorizontal() ? 
						0.345 * Math.sqrt(axis.getModulToAlign().getCartesianAssist().getWidth()) :
							0.476 * Math.sqrt(axis.getModulToAlign().getCartesianAssist().getHeight())));
			}
			return autoCreateTicksNumber(from, to, deltas.get(axis));
		}
		else{
			if(!tickSizePairs.containsKey(axis)){
				setAutoCreationParameters(axis, (int) Math.round(axis.isHorizontal() ? 
						0.345 * Math.sqrt(axis.getModulToAlign().getCartesianAssist().getWidth()) :
							0.476 * Math.sqrt(axis.getModulToAlign().getCartesianAssist().getHeight())));
			}
			return autoCreateTicksTime(from, to, tickSizePairs.get(axis));
		}
	}
	
	public void clearAutoCreationParameters(Axis axis){
		deltas.remove(axis);
		tickSizePairs.remove(axis);
	}
	
	public void setAutoCreationParameters(Axis axis, int tickNoPerVp){
		if(axis.getAxisDataType() == AxisDataType.Number){
			deltas.put(axis, (axis.getMax() - axis.getMin()) / tickNoPerVp);
		}
		else{
			TickSizePair tickTimeAndMultiplier = new TickSizePair(1, TimeUnits.Minute);
			double delta = (axis.getMax() - axis.getMin()) / tickNoPerVp;
			Iterator<TickSizePair> tickSIt = allowedTickSizePairs.iterator();
			TickSizePair prevTickS = tickSIt.next();
			TickSizePair actualTickS;
			while(tickSIt.hasNext()){
				actualTickS = tickSIt.next();
				if(delta < (prevTickS.getDurationMS() +	actualTickS.getDurationMS()) / 2){
					tickTimeAndMultiplier = new TickSizePair(prevTickS);
					break;
				}
				prevTickS = actualTickS;
			}
			
			// special-case the possibility of several years
			if (tickTimeAndMultiplier.getTimeUnit().equals(TimeUnits.Year)) {
				double magn;
				double norm;
				magn = Math.pow(10, Math.floor(Math.log(delta / TimeUnits.Year.durationMS()) / Math.log(10)));
				norm = (delta / TimeUnits.Year.durationMS()) / magn;
				if (norm < 1.5){
					tickTimeAndMultiplier.setMultiplier(1);
				}
				else if (norm < 3){
					tickTimeAndMultiplier.setMultiplier(2);
				}
				else if (norm < 7.5){
					tickTimeAndMultiplier.setMultiplier(5);
				}
				else{
					tickTimeAndMultiplier.setMultiplier(10);
				}
				tickTimeAndMultiplier.setMultiplier(tickTimeAndMultiplier.getMultiplier() * magn);
			}
			tickSizePairs.put(axis, tickTimeAndMultiplier);
		}
	}
	
	protected ArrayList<Tick> autoCreateTicksNumber(double from, double to, double delta){
		int decimal = (int) Math.floor(Math.log10(delta));
		double nDelta = Math.pow(10, decimal);
		double n = delta / nDelta; // delta > nDelta, 1<n<10
		double size;
		if (n < 1.5) {
			size = 1;
		} else if (n < 3) {
			size = 2;
		} else if (n < 7.5) {
			size = 5;
		} else {
			size = 10;
		}
		size *= nDelta;
		double start =  (Double)Math.floor(from / size) * size;
		int i = 0;
		ArrayList<Tick> ticks = new ArrayList<Tick>();
		while (i * size + start <= to) {
			ticks.add(new Tick(i * size + start));
			i++;
		}
		return clearDoubleBugs(ticks,decimal); //TODO still needs the hack? 2012. 08. 24.
	}
	
	@SuppressWarnings("deprecation")
	protected ArrayList<Tick> autoCreateTicksTime(double from, double to, TickSizePair tickTimeAndMultiplier){
		double carry = 0, v = Double.NaN, prev;
		double step = tickTimeAndMultiplier.getDurationMS();
		Date date = new Date((long) from);
		switch (tickTimeAndMultiplier.getTimeUnit()) {
		case Second:
			date.setSeconds((int) floorInBase(date.getSeconds(), tickTimeAndMultiplier.getMultiplier()));
			break;
		case Minute:
			date.setMinutes((int) floorInBase(date.getMinutes(), tickTimeAndMultiplier.getMultiplier()));
			break;
		case Hour:
			date.setHours((int) floorInBase(date.getHours(), tickTimeAndMultiplier.getMultiplier()));
			break;
		case Month:
			date.setMonth((int) floorInBase(date.getMonth(), tickTimeAndMultiplier.getMultiplier()));
			break;
		case Year:
			date.setYear((int) floorInBase(date.getYear() + 1900, tickTimeAndMultiplier.getMultiplier()) - 1900);
			break;
		}
		date = new Date(date.getYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
		
		if (step >= TimeUnits.Minute.durationMS()){
			date.setSeconds(0);
		}
		if (step >= TimeUnits.Hour.durationMS()){
			date.setMinutes(0);
		}
		if (step >= TimeUnits.Day.durationMS()){
			date.setHours(0);
		}
		if (step >= TimeUnits.Day.durationMS() * 4){
			date.setDate(1);
		}
		if (step >= TimeUnits.Year.durationMS()){
			date.setMonth(0);
		}
		ArrayList<Tick> ticks = new ArrayList<Tick>();
		do {
			prev = v;
			v = date.getTime();
			ticks.add(new Tick(v));
			if (tickTimeAndMultiplier.getTimeUnit().equals(TimeUnits.Month)) {
				if (tickTimeAndMultiplier.getMultiplier() < 1) {
					date.setDate(1);
					double start = date.getTime();
					date.setMonth(date.getMonth() + 1);
					double end = date.getTime();
					date.setTime((long) (v + carry * TimeUnits.Hour.durationMS() + (end - start) * tickTimeAndMultiplier.getMultiplier()));
					carry = date.getHours();
					date.setHours(0);
				}
				else{
					date.setMonth((int) (date.getMonth() + tickTimeAndMultiplier.getMultiplier()));
				}
			}
			else if (tickTimeAndMultiplier.getTimeUnit().equals(TimeUnits.Year)) {
				date.setYear((int) (date.getYear() + tickTimeAndMultiplier.getMultiplier()));
			}
			else{
				date.setTime((long) (v + step));
			}
		} 
		while (v < to && v != prev);
		return ticks;
	}

}
